返回博客列表

Python面向对象精解:__new__ vs __init__与单例模式

深入解析 Python 类实例化过程中 __new__ 和 __init__ 的区别,以及如何利用 __new__ 实现单例模式。

2024-01-235 分钟阅读Yaron
#Python#面向对象#单例模式#__new__#__init__

在我们的讨论和代码重构中,涉及了 Python 面向对象编程(OOP)中的几个高级但非常实用的概念。理解它们有助于编写出更健壮、更优雅的代码。

1. new vs. init: 对象的创建与初始化

newinit 是 Python 类实例化过程中两个关键的"魔法方法",但它们的职责有着本质的区别,可以比作建房子装修房子

  • new(cls, ...) - 建筑师:

    • 职责: 创建并返回一个类的实例(一个空对象)。
    • 调用时机: 在 init 之前被调用,是实例创建的第一个环节。
    • 参数: 第一个参数是类本身 cls,而不是实例 self(因为实例此时还未被创建)。
    • 使用场景: 通常我们不需要重写它。最典型的应用场景是实现单例模式(Singleton Pattern),通过在 new 中控制实例的创建过程,确保一个类在全局只有一个实例。例如,一个管理数据库连接池的配置类,就非常适合用单例模式来避免重复初始化。
  • init(self, ...) - 装修工:

    • 职责: 接收由 new 创建好的实例 self,然后对其进行属性初始化
    • 调用时机: 在 new 成功返回一个实例后被调用。
    • 参数: 第一个参数是实例本身 self。
    • 返回值: 不能有返回值 (return None)。
    • 使用场景: 这是绝大多数情况下我们为对象设置初始状态(如 self.name = name)的地方。

通用示例:实现单例模式

class DatabaseConnector:
    _instance = None  # 用于存储唯一实例的类变量

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            print("Creating new instance...")
            # 调用父类的方法来真正创建一个实例
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, dsn):
        # 即使多次调用构造函数,初始化也只应执行一次
        # (通常会在这里添加一个标志位来防止重复初始化)
        self.dsn = dsn

2. @property 装饰器: 将方法伪装成属性

@property 是一个 Pythonic 的语法糖,它能将一个类的方法(method)转换为一个只读属性(read-only attribute)

  • 作用: 允许您像访问普通变量一样调用一个方法,但无需在后面加 ()。
  • 核心优势:
    1. 封装与控制: 它可以为一个"私有"变量(通常以下划线开头,如 _value)提供一个公开、可控的读取接口,同时防止外部代码直接修改这个内部变量,保护了类的内部状态。
    2. 优雅的访问: 调用者可以使用 my_object.value 这样简洁的方式来获取值,而不是 my_object.get_value(),代码更具可读性。
    3. 创建只读属性: 因为只定义了"getter"(@property),没有定义"setter"(@value.setter),所以该属性是只读的,任何赋值尝试都会报错。

通用示例:

class Circle:
    def __init__(self, radius):
        self._radius = radius # 内部属性

    @property
    def diameter(self):
        """这是一个只读属性,它根据半径计算直径"""
        return self._radius * 2

# 使用时
c = Circle(5)
print(f"圆的半径是: {c._radius}")      # 不推荐直接访问内部属性
print(f"圆的直径是: {c.diameter}")    # 正确的方式,看似访问属性,实则调用方法
# c.diameter = 12 # 这行会报错,因为 diameter 是只读的